/************************************************************************/
/*                     Copyright declaration                            */
/*                         (3-clause-BSD)                               */
/************************************************************************/

/*
* Copyright (c) 2009, Roc King
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the OwnWaterloo,Roc King nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#ifdef _MSC_VER
#define _CRT_SECURE_NO_DEPRECATE
#endif
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#ifdef __cplusplus
#include <new>
#endif

#include "utility.h"

#ifdef _WIN32
/** windows, adapt VirtualAlloc,VirtualFree,VirtualProtect */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

static DWORD privilege_map(int privilege) {
    static const DWORD map[] = {
        PAGE_NOACCESS,
        PAGE_READONLY,
        PAGE_READWRITE,
        PAGE_READWRITE,
        PAGE_EXECUTE,
        PAGE_EXECUTE_READ,
        PAGE_EXECUTE_READWRITE,
        PAGE_EXECUTE_READWRITE,
    };
    if (privilege<0
        || privilege>(privilege_read|privilege_write|privilege_execute) )
    {
        assert(!"invalid privilege");
        return 0;
    }
    return map[privilege];
}

static void* VirtualAlloc_allocate(size_t size,int privilege) {
    return VirtualAlloc(0,size,MEM_RESERVE|MEM_COMMIT,privilege_map(privilege));
}

static void VirtualAlloc_deallocate(void* address) {
    VirtualFree(address,0,MEM_RELEASE);
}

/************************************************************************/
/* VirtualProtect promotion                                             */
/************************************************************************/
static int VirtualProtect_promotion(void* address,size_t length,int privilege) {
    DWORD old = 0;
    BOOL ok = VirtualProtect(address,length,privilege_map(privilege),&old);
    return (ok)?0:-1;
}

#else
/** assume posix ,adapt mmap,mummap,mprotect */
#include <sys/mman.h>

static int privilege_map(int privilege) {
    static int map[] = {
        PROT_NONE,
        PROT_READ,
        PROT_WRITE,
        PROT_WRITE|PROT_READ,
        PROT_EXEC,
        PROT_EXEC|PROT_READ,
        PROT_EXEC|PROT_WRITE|PROT_READ,
    };
    if (privilege<0
        || privilege>(privilege_read|privilege_write|privilege_execute) )
    {
        assert(!"invalid privilege");
        return 0;
    }
    return map[privilege];
}


#if defined(__APPLE__)
/* Mac OS uses MAP_ANON insteadof MAP_ANONYMOUS */
#define MAP_ANONYMOUS MAP_ANON
#endif

static void* mmap_allocate(size_t size,int privilege) {
    size_t* p = 0;
    size += sizeof(size_t);
    p = (size_t*)mmap(0,size,privilege_map(privilege)
        ,MAP_PRIVATE | MAP_ANONYMOUS,-1,0);
    if (p == MAP_FAILED) return 0;
    *p = size;
    return p+1;
}

static void mmap_deallocate(void* address) {
    size_t* p = (size_t*)address;
    --p;
    munmap(p,*p);
}

/************************************************************************/
/* mprotect promotion                                                   */
/************************************************************************/
static int mprotect_promotion(void* address,size_t length,int privilege) {
    return mprotect(address,length,privilege_map(privilege));
}

#endif

/************************************************************************/
/* null promotion                                                       */
/************************************************************************/
static int null_promotion(void* address,size_t length,int privilege) {
    (void)address; (void)length; (void)privilege;
    return 0;
}





/************************************************************************/
/* standard storage allocation                                          */
/************************************************************************/
/** heap */
static void* heap_allocate(size_t size,int privilege) {
    (void)privilege;
    return malloc(size);
}

static void heap_deallocate(void* address) {
    free(address);
}


/** stack */
static void* stack_allocate_dummy(size_t size,int privilege) {
    (void)size; (void)privilege;
    return 0;
}

static void stack_deallocate(void* address) {
    (void)address;
}

/** static */
#ifndef CCURRY_TESTING_UTILITY_STATIC_STORAGE_SIZE
#define CCURRY_TESTING_UTILITY_STATIC_STORAGE_SIZE 1212
#endif
static void* local_static_allocate(size_t size,int privilege) {
    static unsigned char local_staic_storage
        [CCURRY_TESTING_UTILITY_STATIC_STORAGE_SIZE];
    static unsigned char* current = local_staic_storage;
    unsigned char* p = current;
    current += size;
    return p;
    (void)privilege;
}
static unsigned char global_static_storage
[CCURRY_TESTING_UTILITY_STATIC_STORAGE_SIZE];
static void* global_static_allocate(size_t size,int privilege) {
    static unsigned char* current = global_static_storage;
    unsigned char* p = current;
    current += size;
    return p;
    (void)privilege;
}

#undef CCURRY_TESTING_UTILITY_STATIC_STORAGE_SIZE

static void local_static_deallocate(void* address) {
    (void)address;
}
static void global_static_deallocate(void* address) {
    (void)address;
}

/** freestore */
#ifdef __cplusplus
namespace {
    void* freestore_allocate(size_t size,int /* ignore */) {
        return ::operator new(size,std::nothrow);
    }
    void  freestore_deallocate(void* address) {
        ::operator delete(address
#if !defined(_MSC_VER) || _MSC_VER>=1400
            // bug in msvc6's header
            ,std::nothrow
#endif
            );
    }
}
#endif

/************************************************************************/
/* implement storage public interface                                   */
/************************************************************************/
struct storage_t {
    void* (*allocate)(size_t size,int privilege);
    void  (*deallocate)(void* address);
};

const storage_t* storage_get(const char* storage_category) {
    static const struct {
        const char* name;
        storage_t storage;
    } pairs[]= {
        {"stack",{stack_allocate_dummy,stack_deallocate}},
        {"heap",{heap_allocate,heap_deallocate}},
        {"local-static",{local_static_allocate,local_static_deallocate}},
        {"global-static",{global_static_allocate,global_static_deallocate}},
#ifdef __cplusplus
        {"freestore",{freestore_allocate,freestore_deallocate}},
#endif
#ifdef _WIN32
        {"VirtualAlloc",{VirtualAlloc_allocate,VirtualAlloc_deallocate}},
#else
        {"mmap",{mmap_allocate,mmap_deallocate}},
#endif
    };
    size_t i = 0;
    for (;i<sizeof(pairs)/sizeof(pairs[0]);++i)
        if (0==strcmp(pairs[i].name,storage_category))
            return &pairs[i].storage;
    return 0;
}

#ifdef __cplusplus
extern "C"
#endif
void* storage_allocate(const storage_t* storage
                      ,size_t size,void* stack_duumy,int privilege)
{
    if (storage->allocate==stack_allocate_dummy)
        return stack_duumy;
    return storage->allocate(size,privilege);
}

#ifdef __cplusplus
extern "C"
#endif
void storage_deallocate(const storage_t* storage,const void* address) {
    storage->deallocate((void*)address);
}

/************************************************************************/
/* implement promotion public interface                                 */
/************************************************************************/
struct promotion_t{
    int (*promote)(void* address,size_t length,int privilege);
};

#ifdef __cplusplus
extern "C"
#endif
const promotion_t* promotion_get(const char* promotion_category)
{
    static const struct  {
        const char* name;
        promotion_t promotion;
    } pairs[] = {
        {"null",{null_promotion}},
#ifdef _WIN32
        {"VirtualProtect",{VirtualProtect_promotion}},
#else
        {"mprotect",{mprotect_promotion}},
#endif
    };
    size_t i = 0;
    for (;i<sizeof(pairs)/sizeof(pairs[0]);++i)
        if (0==strcmp(pairs[i].name,promotion_category))
            return &pairs[i].promotion;
    return 0;
}

#ifdef __cplusplus
extern "C"
#endif
int promotion_promote(const promotion_t* promotion
                     ,const void* address,size_t length,int privilege) {
    return promotion->promote((void*)address,length,privilege);
}


/************************************************************************/
/* implement privilege                                                  */
/************************************************************************/
#ifdef __cplusplus
extern "C"
#endif
int privilege_from_name(const char* name) {
    static const struct  {
        const char* name;
        privilege_mask value;
    } privileges[] = {
        {"read",privilege_read},
        {"write",privilege_write},
        {"execute",privilege_execute},
        {"sentinel",privilege_none},
    };
    size_t i = 0;
    for (;i<sizeof(privileges)/sizeof(privileges[0])-1
        && strcmp(name,privileges[i].name)!=0
        ;++i);
    return privileges[i].value;
}

#ifdef __cplusplus
extern "C"
#endif
int privilege_from_attribute(char* attribute,const char* spliters) {
    int privilege = privilege_none;
    char* token = strtok(attribute,spliters);
    for (;token;token = strtok(0,spliters))
        privilege |= privilege_from_name(token);
    return privilege;
}


#ifdef __cplusplus
extern "C"
#endif
const char* privilege_to_name(int privilege) {
    static char buf[1212] = {0};
    if (privilege<0
        || privilege>(privilege_execute|privilege_write|privilege_read))
    {
        assert(!"invalid privilege");
        return "invalid privilege";
    }
    if (privilege==privilege_none) {
        strncpy(buf,"none",sizeof(buf)-1);
        return buf;
    }
    buf[0] = '\0';
    if (privilege & privilege_read)
        strncpy(buf,"read",12);
    if (privilege & privilege_write)
        strncat(buf,buf[0]=='\0'?"write":",write",12);
    if (privilege & privilege_execute)
        strncat(buf,buf[0]=='\0'?"execute":",execute",12);
    return buf;
}
